1
//--------------------------------------------------------------------------
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // File: TaskExtensions.cs
7 //--------------------------------------------------------------------------
10 using System
.Windows
.Threading
;
12 namespace System
.Threading
.Tasks
14 /// <summary>Extensions methods for Task.</summary>
15 public static class TaskExtrasExtensions
17 #region ContinueWith accepting TaskFactory
18 /// <summary>Creates a continuation task using the specified TaskFactory.</summary>
19 /// <param name="task">The antecedent Task.</param>
20 /// <param name="continuationAction">The continuation action.</param>
21 /// <param name="factory">The TaskFactory.</param>
22 /// <returns>A continuation task.</returns>
23 public static Task
ContinueWith(
24 this Task task
, Action
<Task
> continuationAction
, TaskFactory factory
)
26 return task
.ContinueWith(continuationAction
, factory
.CancellationToken
, factory
.ContinuationOptions
, factory
.Scheduler
);
29 /// <summary>Creates a continuation task using the specified TaskFactory.</summary>
30 /// <param name="task">The antecedent Task.</param>
31 /// <param name="continuationFunction">The continuation function.</param>
32 /// <param name="factory">The TaskFactory.</param>
33 /// <returns>A continuation task.</returns>
34 public static Task
<TResult
> ContinueWith
<TResult
>(
35 this Task task
, Func
<Task
, TResult
> continuationFunction
, TaskFactory factory
)
37 return task
.ContinueWith(continuationFunction
, factory
.CancellationToken
, factory
.ContinuationOptions
, factory
.Scheduler
);
41 #region ContinueWith accepting TaskFactory<TResult>
42 /// <summary>Creates a continuation task using the specified TaskFactory.</summary>
43 /// <param name="task">The antecedent Task.</param>
44 /// <param name="continuationAction">The continuation action.</param>
45 /// <param name="factory">The TaskFactory.</param>
46 /// <returns>A continuation task.</returns>
47 public static Task ContinueWith
<TResult
>(
48 this Task
<TResult
> task
, Action
<Task
<TResult
>> continuationAction
, TaskFactory
<TResult
> factory
)
50 return task
.ContinueWith(continuationAction
, factory
.CancellationToken
, factory
.ContinuationOptions
, factory
.Scheduler
);
53 /// <summary>Creates a continuation task using the specified TaskFactory.</summary>
54 /// <param name="task">The antecedent Task.</param>
55 /// <param name="continuationFunction">The continuation function.</param>
56 /// <param name="factory">The TaskFactory.</param>
57 /// <returns>A continuation task.</returns>
58 public static Task
<TNewResult
> ContinueWith
<TResult
, TNewResult
>(
59 this Task
<TResult
> task
, Func
<Task
<TResult
>, TNewResult
> continuationFunction
, TaskFactory
<TResult
> factory
)
61 return task
.ContinueWith(continuationFunction
, factory
.CancellationToken
, factory
.ContinuationOptions
, factory
.Scheduler
);
65 #region ToAsync(AsyncCallback, object)
67 /// Creates a Task that represents the completion of another Task, and
68 /// that schedules an AsyncCallback to run upon completion.
70 /// <param name="task">The antecedent Task.</param>
71 /// <param name="callback">The AsyncCallback to run.</param>
72 /// <param name="state">The object state to use with the AsyncCallback.</param>
73 /// <returns>The new task.</returns>
74 public static Task
ToAsync(this Task task
, AsyncCallback callback
, object state
)
76 if (task
== null) throw new ArgumentNullException("task");
78 var tcs
= new TaskCompletionSource
<object>(state
);
79 task
.ContinueWith(_
=>
81 tcs
.SetFromTask(task
);
82 if (callback
!= null) callback(tcs
.Task
);
88 /// Creates a Task that represents the completion of another Task, and
89 /// that schedules an AsyncCallback to run upon completion.
91 /// <param name="task">The antecedent Task.</param>
92 /// <param name="callback">The AsyncCallback to run.</param>
93 /// <param name="state">The object state to use with the AsyncCallback.</param>
94 /// <returns>The new task.</returns>
95 public static Task
<TResult
> ToAsync
<TResult
>(this Task
<TResult
> task
, AsyncCallback callback
, object state
)
97 if (task
== null) throw new ArgumentNullException("task");
99 var tcs
= new TaskCompletionSource
<TResult
>(state
);
100 task
.ContinueWith(_
=>
102 tcs
.SetFromTask(task
);
103 if (callback
!= null) callback(tcs
.Task
);
109 #region Exception Handling
110 /// <summary>Suppresses default exception handling of a Task that would otherwise reraise the exception on the finalizer thread.</summary>
111 /// <param name="task">The Task to be monitored.</param>
112 /// <returns>The original Task.</returns>
113 public static Task
IgnoreExceptions(this Task task
)
115 task
.ContinueWith(t
=> { var ignored = t.Exception; }
,
116 CancellationToken
.None
,
117 TaskContinuationOptions
.ExecuteSynchronously
| TaskContinuationOptions
.OnlyOnFaulted
,
118 TaskScheduler
.Default
);
122 /// <summary>Suppresses default exception handling of a Task that would otherwise reraise the exception on the finalizer thread.</summary>
123 /// <param name="task">The Task to be monitored.</param>
124 /// <returns>The original Task.</returns>
125 public static Task
<T
> IgnoreExceptions
<T
>(this Task
<T
> task
)
127 return (Task
<T
>)((Task
)task
).IgnoreExceptions();
130 /// <summary>Fails immediately when an exception is encountered.</summary>
131 /// <param name="task">The Task to be monitored.</param>
132 /// <returns>The original Task.</returns>
133 public static Task
FailFastOnException(this Task task
)
135 task
.ContinueWith(t
=> Environment
.FailFast("A task faulted.", t
.Exception
),
136 CancellationToken
.None
,
137 TaskContinuationOptions
.ExecuteSynchronously
| TaskContinuationOptions
.OnlyOnFaulted
,
138 TaskScheduler
.Default
);
142 /// <summary>Fails immediately when an exception is encountered.</summary>
143 /// <param name="task">The Task to be monitored.</param>
144 /// <returns>The original Task.</returns>
145 public static Task
<T
> FailFastOnException
<T
>(this Task
<T
> task
)
147 return (Task
<T
>)((Task
)task
).FailFastOnException();
150 /// <summary>Propagates any exceptions that occurred on the specified task.</summary>
151 /// <param name="task">The Task whose exceptions are to be propagated.</param>
152 public static void PropagateExceptions(this Task task
)
154 if (!task
.IsCompleted
) throw new InvalidOperationException("The task has not completed.");
155 if (task
.IsFaulted
) task
.Wait();
158 /// <summary>Propagates any exceptions that occurred on the specified tasks.</summary>
159 /// <param name="task">The Tassk whose exceptions are to be propagated.</param>
160 public static void PropagateExceptions(this Task
[] tasks
)
162 if (tasks
== null) throw new ArgumentNullException("tasks");
163 if (tasks
.Any(t
=> t
== null)) throw new ArgumentException("tasks");
164 if (tasks
.Any(t
=> !t
.IsCompleted
)) throw new InvalidOperationException("A task has not completed.");
170 /// <summary>Creates an IObservable that represents the completion of a Task.</summary>
171 /// <typeparam name="TResult">Specifies the type of data returned by the Task.</typeparam>
172 /// <param name="task">The Task to be represented as an IObservable.</param>
173 /// <returns>An IObservable that represents the completion of the Task.</returns>
174 public static IObservable
<TResult
> ToObservable
<TResult
>(this Task
<TResult
> task
)
176 if (task
== null) throw new ArgumentNullException("task");
177 return new TaskObservable
<TResult
> { _task = task }
;
180 /// <summary>An implementation of IObservable that wraps a Task.</summary>
181 /// <typeparam name="TResult">The type of data returned by the task.</typeparam>
182 private class TaskObservable
<TResult
> : IObservable
<TResult
>
184 internal Task
<TResult
> _task
;
186 public IDisposable
Subscribe(IObserver
<TResult
> observer
)
188 // Validate arguments
189 if (observer
== null) throw new ArgumentNullException("observer");
191 // Support cancelling the continuation if the observer is unsubscribed
192 var cts
= new CancellationTokenSource();
194 // Create a continuation to pass data along to the observer
195 _task
.ContinueWith(t
=>
199 case TaskStatus
.RanToCompletion
:
200 observer
.OnNext(_task
.Result
);
201 observer
.OnCompleted();
204 case TaskStatus
.Faulted
:
205 observer
.OnError(_task
.Exception
);
208 case TaskStatus
.Canceled
:
209 observer
.OnError(new TaskCanceledException(t
));
214 // Support unsubscribe simply by canceling the continuation if it hasn't yet run
215 return new CancelOnDispose { Source = cts }
;
219 /// <summary>Translate a call to IDisposable.Dispose to a CancellationTokenSource.Cancel.</summary>
220 private class CancelOnDispose
: IDisposable
222 internal CancellationTokenSource Source
;
223 void IDisposable
.Dispose() { Source.Cancel(); }
228 /// <summary>Creates a new Task that mirrors the supplied task but that will be canceled after the specified timeout.</summary>
229 /// <typeparam name="TResult">Specifies the type of data contained in the task.</typeparam>
230 /// <param name="task">The task.</param>
231 /// <param name="timeout">The timeout.</param>
232 /// <returns>The new Task that may time out.</returns>
233 public static Task
WithTimeout(this Task task
, TimeSpan timeout
)
235 var result
= new TaskCompletionSource
<object>(task
.AsyncState
);
236 var timer
= new Timer(state
=> ((TaskCompletionSource
<object>)state
).TrySetCanceled(), result
, timeout
, TimeSpan
.FromMilliseconds(-1));
237 task
.ContinueWith(t
=>
240 result
.TrySetFromTask(t
);
241 }, TaskContinuationOptions
.ExecuteSynchronously
);
245 /// <summary>Creates a new Task that mirrors the supplied task but that will be canceled after the specified timeout.</summary>
246 /// <typeparam name="TResult">Specifies the type of data contained in the task.</typeparam>
247 /// <param name="task">The task.</param>
248 /// <param name="timeout">The timeout.</param>
249 /// <returns>The new Task that may time out.</returns>
250 public static Task
<TResult
> WithTimeout
<TResult
>(this Task
<TResult
> task
, TimeSpan timeout
)
252 var result
= new TaskCompletionSource
<TResult
>(task
.AsyncState
);
253 var timer
= new Timer(state
=> ((TaskCompletionSource
<TResult
>)state
).TrySetCanceled(), result
, timeout
, TimeSpan
.FromMilliseconds(-1));
254 task
.ContinueWith(t
=>
257 result
.TrySetFromTask(t
);
258 }, TaskContinuationOptions
.ExecuteSynchronously
);
265 /// Ensures that a parent task can't transition into a completed state
266 /// until the specified task has also completed, even if it's not
267 /// already a child task.
269 /// <param name="task">The task to attach to the current task as a child.</param>
270 public static void AttachToParent(this Task task
)
272 if (task
== null) throw new ArgumentNullException("task");
273 task
.ContinueWith(t
=> t
.Wait(), CancellationToken
.None
,
274 TaskContinuationOptions
.AttachedToParent
|
275 TaskContinuationOptions
.ExecuteSynchronously
, TaskScheduler
.Default
);
280 /// <summary>Waits for the task to complete execution, pumping in the meantime.</summary>
281 /// <param name="task">The task for which to wait.</param>
282 /// <remarks>This method is intended for usage with Windows Presentation Foundation.</remarks>
283 public static void WaitWithPumping(this Task task
)
285 if (task
== null) throw new ArgumentNullException("task");
286 var nestedFrame
= new DispatcherFrame();
287 task
.ContinueWith(_
=> nestedFrame
.Continue
= false);
288 Dispatcher
.PushFrame(nestedFrame
);
292 /// <summary>Waits for the task to complete execution, returning the task's final status.</summary>
293 /// <param name="task">The task for which to wait.</param>
294 /// <returns>The completion status of the task.</returns>
295 /// <remarks>Unlike Wait, this method will not throw an exception if the task ends in the Faulted or Canceled state.</remarks>
296 public static TaskStatus
WaitForCompletionStatus(this Task task
)
298 if (task
== null) throw new ArgumentNullException("task");
299 ((IAsyncResult
)task
).AsyncWaitHandle
.WaitOne();
305 /// <summary>Creates a task that represents the completion of a follow-up action when a task completes.</summary>
306 /// <param name="task">The task.</param>
307 /// <param name="next">The action to run when the task completes.</param>
308 /// <returns>The task that represents the completion of both the task and the action.</returns>
309 public static Task
Then(this Task task
, Action next
)
311 if (task
== null) throw new ArgumentNullException("task");
312 if (next
== null) throw new ArgumentNullException("next");
314 var tcs
= new TaskCompletionSource
<object>();
315 task
.ContinueWith(delegate
317 if (task
.IsFaulted
) tcs
.TrySetException(task
.Exception
.InnerExceptions
);
318 else if (task
.IsCanceled
) tcs
.TrySetCanceled();
324 tcs
.TrySetResult(null);
326 catch (Exception exc
) { tcs.TrySetException(exc); }
328 }, TaskScheduler
.Default
);
332 /// <summary>Creates a task that represents the completion of a follow-up function when a task completes.</summary>
333 /// <param name="task">The task.</param>
334 /// <param name="next">The function to run when the task completes.</param>
335 /// <returns>The task that represents the completion of both the task and the function.</returns>
336 public static Task
<TResult
> Then
<TResult
>(this Task task
, Func
<TResult
> next
)
338 if (task
== null) throw new ArgumentNullException("task");
339 if (next
== null) throw new ArgumentNullException("next");
341 var tcs
= new TaskCompletionSource
<TResult
>();
342 task
.ContinueWith(delegate
344 if (task
.IsFaulted
) tcs
.TrySetException(task
.Exception
.InnerExceptions
);
345 else if (task
.IsCanceled
) tcs
.TrySetCanceled();
351 tcs
.TrySetResult(result
);
353 catch (Exception exc
) { tcs.TrySetException(exc); }
355 }, TaskScheduler
.Default
);
359 /// <summary>Creates a task that represents the completion of a follow-up action when a task completes.</summary>
360 /// <param name="task">The task.</param>
361 /// <param name="next">The action to run when the task completes.</param>
362 /// <returns>The task that represents the completion of both the task and the action.</returns>
363 public static Task Then
<TResult
>(this Task
<TResult
> task
, Action
<TResult
> next
)
365 if (task
== null) throw new ArgumentNullException("task");
366 if (next
== null) throw new ArgumentNullException("next");
368 var tcs
= new TaskCompletionSource
<object>();
369 task
.ContinueWith(delegate
371 if (task
.IsFaulted
) tcs
.TrySetException(task
.Exception
.InnerExceptions
);
372 else if (task
.IsCanceled
) tcs
.TrySetCanceled();
378 tcs
.TrySetResult(null);
380 catch (Exception exc
) { tcs.TrySetException(exc); }
382 }, TaskScheduler
.Default
);
386 /// <summary>Creates a task that represents the completion of a follow-up function when a task completes.</summary>
387 /// <param name="task">The task.</param>
388 /// <param name="next">The function to run when the task completes.</param>
389 /// <returns>The task that represents the completion of both the task and the function.</returns>
390 public static Task
<TNewResult
> Then
<TResult
, TNewResult
>(this Task
<TResult
> task
, Func
<TResult
, TNewResult
> next
)
392 if (task
== null) throw new ArgumentNullException("task");
393 if (next
== null) throw new ArgumentNullException("next");
395 var tcs
= new TaskCompletionSource
<TNewResult
>();
396 task
.ContinueWith(delegate
398 if (task
.IsFaulted
) tcs
.TrySetException(task
.Exception
.InnerExceptions
);
399 else if (task
.IsCanceled
) tcs
.TrySetCanceled();
404 var result
= next(task
.Result
);
405 tcs
.TrySetResult(result
);
407 catch (Exception exc
) { tcs.TrySetException(exc); }
409 }, TaskScheduler
.Default
);
413 /// <summary>Creates a task that represents the completion of a second task when a first task completes.</summary>
414 /// <param name="task">The first task.</param>
415 /// <param name="next">The function that produces the second task.</param>
416 /// <returns>The task that represents the completion of both the first and second task.</returns>
417 public static Task
Then(this Task task
, Func
<Task
> next
)
419 if (task
== null) throw new ArgumentNullException("task");
420 if (next
== null) throw new ArgumentNullException("next");
422 var tcs
= new TaskCompletionSource
<object>();
423 task
.ContinueWith(delegate
425 // When the first task completes, if it faulted or was canceled, bail
426 if (task
.IsFaulted
) tcs
.TrySetException(task
.Exception
.InnerExceptions
);
427 else if (task
.IsCanceled
) tcs
.TrySetCanceled();
430 // Otherwise, get the next task. If it's null, bail. If not,
431 // when it's done we'll have our result.
432 try { next().ContinueWith(t => tcs.TrySetFromTask(t), TaskScheduler.Default); }
433 catch (Exception exc
) { tcs.TrySetException(exc); }
435 }, TaskScheduler
.Default
);
439 /// <summary>Creates a task that represents the completion of a second task when a first task completes.</summary>
440 /// <param name="task">The first task.</param>
441 /// <param name="next">The function that produces the second task based on the result of the first task.</param>
442 /// <returns>The task that represents the completion of both the first and second task.</returns>
443 public static Task Then
<T
>(this Task
<T
> task
, Func
<T
, Task
> next
)
445 if (task
== null) throw new ArgumentNullException("task");
446 if (next
== null) throw new ArgumentNullException("next");
448 var tcs
= new TaskCompletionSource
<object>();
449 task
.ContinueWith(delegate
451 // When the first task completes, if it faulted or was canceled, bail
452 if (task
.IsFaulted
) tcs
.TrySetException(task
.Exception
.InnerExceptions
);
453 else if (task
.IsCanceled
) tcs
.TrySetCanceled();
456 // Otherwise, get the next task. If it's null, bail. If not,
457 // when it's done we'll have our result.
458 try { next(task.Result).ContinueWith(t => tcs.TrySetFromTask(t), TaskScheduler.Default); }
459 catch (Exception exc
) { tcs.TrySetException(exc); }
461 }, TaskScheduler
.Default
);
465 /// <summary>Creates a task that represents the completion of a second task when a first task completes.</summary>
466 /// <param name="task">The first task.</param>
467 /// <param name="next">The function that produces the second task.</param>
468 /// <returns>The task that represents the completion of both the first and second task.</returns>
469 public static Task
<TResult
> Then
<TResult
>(this Task task
, Func
<Task
<TResult
>> next
)
471 if (task
== null) throw new ArgumentNullException("task");
472 if (next
== null) throw new ArgumentNullException("next");
474 var tcs
= new TaskCompletionSource
<TResult
>();
475 task
.ContinueWith(delegate
477 // When the first task completes, if it faulted or was canceled, bail
478 if (task
.IsFaulted
) tcs
.TrySetException(task
.Exception
.InnerExceptions
);
479 else if (task
.IsCanceled
) tcs
.TrySetCanceled();
482 // Otherwise, get the next task. If it's null, bail. If not,
483 // when it's done we'll have our result.
484 try { next().ContinueWith(t => tcs.TrySetFromTask(t), TaskScheduler.Default); }
485 catch (Exception exc
) { tcs.TrySetException(exc); }
487 }, TaskScheduler
.Default
);
491 /// <summary>Creates a task that represents the completion of a second task when a first task completes.</summary>
492 /// <param name="task">The first task.</param>
493 /// <param name="next">The function that produces the second task based on the result of the first.</param>
494 /// <returns>The task that represents the completion of both the first and second task.</returns>
495 public static Task
<TNewResult
> Then
<TResult
, TNewResult
>(this Task
<TResult
> task
, Func
<TResult
, Task
<TNewResult
>> next
)
497 if (task
== null) throw new ArgumentNullException("task");
498 if (next
== null) throw new ArgumentNullException("next");
500 var tcs
= new TaskCompletionSource
<TNewResult
>();
501 task
.ContinueWith(delegate
503 // When the first task completes, if it faulted or was canceled, bail
504 if (task
.IsFaulted
) tcs
.TrySetException(task
.Exception
.InnerExceptions
);
505 else if (task
.IsCanceled
) tcs
.TrySetCanceled();
508 // Otherwise, get the next task. If it's null, bail. If not,
509 // when it's done we'll have our result.
510 try { next(task.Result).ContinueWith(t => tcs.TrySetFromTask(t), TaskScheduler.Default); }
511 catch (Exception exc
) { tcs.TrySetException(exc); }
513 }, TaskScheduler
.Default
);